package com.xuecheng.media.utils;

import com.fasterxml.jackson.databind.util.ArrayIterator;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.po.MediaFiles;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Date:2023/8/18
 * Author:丐版小杨哥
 * Description: 用于文件上传工具类
 */
@Component
@Slf4j
public class MinIoFileUtils {
    public static final String PUBLIC="pub";
    public static final String PRIVATE="pri";
    @Autowired
    private MediaFilesMapper mediaFilesMapper;
    @Autowired
    private MinioClient minioClient;

    /**
     * 获取文件拓展名
     * @param extension
     * @return
     */
    public String getMimeType(String extension){
        if(extension==null) {
            extension = "";
        }
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        String mimeType= MediaType.APPLICATION_OCTET_STREAM_VALUE;
        if (extensionMatch!=null){
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }

    /**
     * 获取文件的MD5值
     * @param file 文件的路径
     * @return 返回文件的MD5值
     */
    public String getFileMd5(File file){
        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            String fileMd5 = DigestUtils.md5Hex(fileInputStream);
            return fileMd5;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 判断桶是否存在
     * @param bucketFiles
     * @return
     */
    public boolean existsBucket(String bucketFiles) {
        boolean b=false;
        try {
            b= minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketFiles).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return b;
    }

    /**
     * 创建桶
     * @param bucketFiles
     */
    public void createBucket(String bucketFiles) {
        try {
            minioClient.makeBucket(
                    MakeBucketArgs.builder().
                            bucket(bucketFiles).
                            build()
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 并且设置访问权限为public PUBLIC默认公开 PRIVATE私有
     * @param bucketFiles
     * @param authority
     */
    public void setBucketAuthority(String bucketFiles,String authority){
        String pub = "{\"Version\":\"2012-10-17\"," +
                "\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" +
                "{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," +
                "\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketFiles +
                "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" +
                bucketFiles +
                "/*\"]}]}";

        String pri="{\"Version\":\"2012-10-17\",\"Statement\":[]}";
        try {
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder()
                            .bucket(bucketFiles)
                            .config(authority.equals("pub")?pub:pri)
                            .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 传入文件的Md5值来获取分块文件的路径
     * @param fileMd5
     * @return
     */
    public String getChunkFileFolderPath(String fileMd5){
        return fileMd5.substring(0,1)+"/"+fileMd5.substring(1,2)+"/"+fileMd5+"/chunk/";
    }

    /**
     * 上传文件到minio
     * @param localFilePath 本地文件路径
     * @param bucket 桶
     * @param mimeType 文件类型
     * @param objectName 文件名
     * @return
     */
    public boolean addMediaFileToMinIo(String localFilePath,String bucket,String mimeType,String objectName){
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)//桶名称
                    .filename(localFilePath)//上传本地文件路径
                    .object(objectName)//上传命名称
                    .contentType(mimeType)
                    .build();
            minioClient.uploadObject(uploadObjectArgs);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件出错->bucket:{},objectName:{},错误信息:{}",bucket,objectName,e.getMessage());
        }
        return false;
    }

    /**
     * 获取文件名
     * @return 2023/7/20的形式返回字符串
     */
    public String getDefaultFolderPath(){
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
        String folder = format.format(new Date()).replace("-", "/")+"/";
        return folder;
    }

    /**
     * 删除图片
     * @throws Exception
     */
    @Transactional
    public void deleteImg (String bucket,String url) {
        RemoveObjectArgs test = RemoveObjectArgs.builder()
                .bucket(bucket)
                .object(url)
                .build();
        try {
            minioClient.removeObject(test);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件上传到服务器之后把信息记录到数据库中
     * @param companyId 公司id
     * @param fileMd5 文件MD5值
     * @param uploadFileParamsDto 文件基本信息
     * @param bucket 桶名称
     * @param objectName 文件名
     * @return
     */
    @Transactional
    public MediaFiles addMediaFileToDB(Long companyId, String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName) {
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles==null){
            mediaFiles = new MediaFiles();
            BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
            mediaFiles.setId(fileMd5);
            mediaFiles.setCompanyId(companyId);
            mediaFiles.setBucket(bucket);
            mediaFiles.setFilePath("/"+bucket+"/"+objectName);
            mediaFiles.setFileId(fileMd5);
            mediaFiles.setUrl("/"+bucket+"/"+objectName);
            mediaFiles.setCreateDate(LocalDateTime.now());
            mediaFiles.setStatus("1");
            mediaFiles.setAuditStatus("002003");
            int insert = mediaFilesMapper.insert(mediaFiles);
            if (insert<=0){
                log.error("上传文件出错->bucket:{},objectName:{}",bucket,objectName);
                return null;
            }
            return mediaFiles;
        }
        //更新
        mediaFiles.setChangeDate(LocalDateTime.now());
        String folder=getDefaultFolderPath();
        String newObjectName=folder+objectName.substring(11);
        mediaFiles.setUrl("/"+bucket+"/"+newObjectName);
        mediaFiles.setFilePath("/"+bucket+"/"+newObjectName);
        mediaFilesMapper.updateById(mediaFiles);
        //更新到分布式待处理任务表中
        addWaitingTask(mediaFiles);
        return mediaFiles;
    }

    /**
     * 文件合并
     * @throws Exception
     */
    public boolean minioMerge(String bucket,int chunkTotal,String chunkPath,String objectName){
        List<ComposeSource> composeSourceList=new ArrayList<ComposeSource>();
        for (int i=0;i<chunkTotal;i++){
            ComposeSource source = ComposeSource.builder().
                    bucket(bucket).
                    object(chunkPath+i).build();
            composeSourceList.add(source);
        }
        ComposeObjectArgs build = ComposeObjectArgs.builder()
                .bucket(bucket)
                .object(objectName)
                .sources(composeSourceList)
                .build();
        try {
            minioClient.composeObject(build);
            return true;
        } catch (Exception e) {
            log.error("合并文件出错,桶:{},文件:{},错误信息:{}",bucket,objectName,e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取文件合并的地址
     * @param MD5
     * @param fileExt
     * @return
     */
    public String getFilePathByMD5(String MD5,String fileExt){
        return MD5.substring(0,1)+"/"+MD5.substring(1,2)+"/"+MD5+fileExt;
    }

    /**
     * 获取下载文件的流
     * @param bucket
     * @param objectName
     * @return
     */
    public File downLoadFileFromMinIo(String bucket,String objectName){
        File tempFile=null;
        FilterInputStream fileInputStream =null;
        try {
            GetObjectArgs objectArgs = GetObjectArgs.builder().
                    bucket(bucket).
                    object(objectName).
                    build();
            fileInputStream = minioClient.getObject(objectArgs);
            tempFile = File.createTempFile("minio", ".merge");
            FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
            IOUtils.copy(fileInputStream,fileOutputStream);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("流拷贝异常");
        }finally {
            if (fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (Exception e) {
                    log.error("流关闭异常");
                }
            }
        }
        return tempFile;
    }

    /**
     * 删除minio临时分块
     * @param chunkFileFolderPath
     * @param chunkTotal
     */
    public void clearChunkFiles(String chunkFileFolderPath ,int chunkTotal,String bucket){
        Iterable<DeleteObject> iterable = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> new DeleteObject(chunkFileFolderPath + i))
                .collect(Collectors.toList());
        /*List<DeleteObject> deleteObjects = new ArrayList<>();*/
        RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder()
                .bucket(bucket)
                .objects(iterable)
                .build();
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
        //想要真正删除文件需要遍历一下
        results.forEach(s-> {
            try {
                s.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 将视频任务添加到数据库中
     * @param mediaFiles
     */
    public void addWaitingTask(MediaFiles mediaFiles){
        String filename = mediaFiles.getFilename();
        String extention = filename.substring(filename.lastIndexOf("."));
        String mimeType = getMimeType(extention);
        if (mimeType.equals("video/x-msvideo")){
            MediaFiles mediaFilesToDb = new MediaFiles();
            BeanUtils.copyProperties(mediaFiles,mediaFilesToDb);
            mediaFilesToDb.setCreateDate(LocalDateTime.now());
            mediaFilesMapper.insert(mediaFilesToDb);
        }
    }
}